package com.zf.springlearn;

import com.zf.springlearn.lifecycle.OneBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.metrics.StartupStep;

import java.util.List;

@SpringBootApplication
public class SpringLearnApplication {

    public static void main(String[] args) {
        MySpringApplication mySpringApplication = new MySpringApplication(SpringLearnApplication.class);
        mySpringApplication.run(args);
    }

    public static class MySpringApplication extends SpringApplication {
        public MySpringApplication(Class<?>... primarySources) {
            super(primarySources);
        }

        @Override
        protected ConfigurableApplicationContext createApplicationContext() {
            return new MyApplicationContext();
        }
    }

    public static class MyApplicationContext extends AnnotationConfigApplicationContext {

        private final Object startupShutdownMonitor = new Object();

        @Override
        public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                StartupStep contextRefresh = getApplicationStartup().start("spring.context.refresh");

                // Prepare this context for refreshing.
                prepareRefresh();

                // Tell the subclass to refresh the internal bean factory.
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

                // Prepare the bean factory for use in this context.
                prepareBeanFactory(beanFactory);

                try {
                    // Allows post-processing of the bean factory in context subclasses.
                    postProcessBeanFactory(beanFactory);

                    StartupStep beanPostProcess = getApplicationStartup().start("spring.context.beans.post-process");
                    // Invoke factory processors registered as beans in the context.
                    invokeBeanFactoryPostProcessors(beanFactory);

                    // Register bean processors that intercept bean creation.
                    registerBeanPostProcessors(beanFactory);

                    // Register debug processor that watches the changes of bean properties
                    registerDebugProcessor(beanFactory);

                    beanPostProcess.end();

                    // Initialize message source for this context.
                    initMessageSource();

                    // Initialize event multicaster for this context.
                    initApplicationEventMulticaster();

                    // Initialize other special beans in specific context subclasses.
                    onRefresh();

                    // Check for listener beans and register them.
                    registerListeners();

                    // Instantiate all remaining (non-lazy-init) singletons.
                    finishBeanFactoryInitialization(beanFactory);

                    // Last step: publish corresponding event.
                    finishRefresh();
                } catch (BeansException ex) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Exception encountered during context initialization - " +
                                "cancelling refresh attempt: " + ex);
                    }

                    // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();

                    // Reset 'active' flag.
                    cancelRefresh(ex);

                    // Propagate exception to caller.
                    throw ex;
                } finally {
                    // Reset common introspection caches in Spring's core, since we
                    // might not ever need metadata for singleton beans anymore...
                    resetCommonCaches();
                    contextRefresh.end();
                }
            }
        }

        private void registerDebugProcessor(ConfigurableListableBeanFactory beanFactory){
            List<BeanPostProcessor> beanPostProcessors = ((DefaultListableBeanFactory) beanFactory).getBeanPostProcessors();
            BeanPostProcessor debugProcessor = null;
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                if (beanPostProcessor instanceof OneBean.DebugProcessor) {
                    debugProcessor = beanPostProcessor;
                }
            }
            if (debugProcessor != null) {
                beanPostProcessors.remove(debugProcessor);
            }
            int size = beanPostProcessors.size();
            for (int i = 0; i < size; i++) {
                int index = 2 * i + 1;
                beanPostProcessors.add(index, debugProcessor);
            }
        }
    }
}
